﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

namespace JESAI.Common.Util.IDGenerator
{
    [Serializable]
    public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>
    {
        private static readonly DateTime __unixEpoch;
        private static readonly long __dateTimeMaxValueMillisecondsSinceEpoch;
        private static readonly long __dateTimeMinValueMillisecondsSinceEpoch;
        private static ObjectId __emptyInstance = default(ObjectId);
        private static int __staticMachine;
        private static short __staticPid;
        private static int __staticIncrement; // 生成新ObjectId时，高字节将被屏蔽
        private static uint[] _lookup32 = Enumerable.Range(0, 256).Select(i =>
        {
            string s = i.ToString("x2");
            return ((uint)s[0]) + ((uint)s[1] << 16);
        }).ToArray();

        //我们使用14个字节而不是12个字节来将ObjectId保存在内存中，但是与字节[]不同的是，堆中没有其他对象
        //这个类之外的任何人都看不到额外的两个字节，它们为我们提供了相当大的简化
        //此表示的另一个优点是，它将序列化为JSON，而不会出现任何64位溢出问题
        private int _timestamp;
        private int _machine;
        private short _pid;
        private int _increment;

        #region 静态构造函数
        static ObjectId()
        {
            __unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            __dateTimeMaxValueMillisecondsSinceEpoch = (DateTime.MaxValue - __unixEpoch).Ticks / 10000;
            __dateTimeMinValueMillisecondsSinceEpoch = (DateTime.MinValue - __unixEpoch).Ticks / 10000;
            __staticMachine = GetMachineHash();
            __staticIncrement = (new Random()).Next();
            __staticPid = (short)GetCurrentProcessId();
        }
        #endregion

        #region 构造函数
        public ObjectId(byte[] bytes)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
        }
        /// <summary>
        /// 初始化ObjectId类的新实例。
        /// </summary>
        /// <param name="timestamp">时间戳（表示为日期时间）。</param>
        /// <param name="machine">机器哈希码</param>
        /// <param name="pid">PID.</param>
        /// <param name="increment">增量</param>
        public ObjectId(DateTime timestamp, int machine, short pid, int increment)
            : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
        {
        }
        /// <summary>
        /// 初始化ObjectId类的新实例
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <param name="machine">机器哈希码</param>
        /// <param name="pid">PID.</param>
        /// <param name="increment">增量</param>
        public ObjectId(int timestamp, int machine, short pid, int increment)
        {
            if ((machine & 0xff000000) != 0)
            {
                throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
            }
            if ((increment & 0xff000000) != 0)
            {
                throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
            }

            _timestamp = timestamp;
            _machine = machine;
            _pid = pid;
            _increment = increment;
        }

        /// <summary>
        /// 初始化ObjectId类的新实例
        /// </summary>
        /// <param name="value">The value.</param>
        public ObjectId(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment);
        }
        #endregion

        #region 公开静态属性
        /// <summary>
        /// 获取值为空的Objcetid实例
        /// </summary>
        public static ObjectId Empty
        {
            get { return __emptyInstance; }
        }
        #endregion

        #region 公开属性
        /// <summary>
        /// 得到时间戳
        /// </summary>
        public int Timestamp
        {
            get { return _timestamp; }
        }

        /// <summary>
        /// 得到机器码
        /// </summary>
        public int Machine
        {
            get { return _machine; }
        }

        /// <summary>
        /// 得到PID.
        /// </summary>
        public short Pid
        {
            get { return _pid; }
        }

        /// <summary>
        /// 得到增量
        /// </summary>
        public int Increment
        {
            get { return _increment; }
        }

        /// <summary>
        /// Gets the creation time (derived from the timestamp).
        /// 获得创建时间，由时间戳驱动
        /// </summary>
        public DateTime CreationTime
        {
            get { return __unixEpoch.AddSeconds(_timestamp); }
        }
        #endregion

        #region 公开运算符
        /// <summary>
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>如果第一个ObjectId比第二个小，则返回true</returns>
        public static bool operator <(ObjectId lhs, ObjectId rhs)
        {
            return lhs.CompareTo(rhs) < 0;
        }

        /// <summary>
        /// Compares two ObjectIds.
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>如果第一个小于或者等于第二个Objcetid ,则返回True</returns>
        public static bool operator <=(ObjectId lhs, ObjectId rhs)
        {
            return lhs.CompareTo(rhs) <= 0;
        }

        /// <summary>
        /// Compares two ObjectIds.
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>True if the two ObjectIds are equal.</returns>
        public static bool operator ==(ObjectId lhs, ObjectId rhs)
        {
            return lhs.Equals(rhs);
        }

        /// <summary>
        /// Compares two ObjectIds.
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>True if the two ObjectIds are not equal.</returns>
        public static bool operator !=(ObjectId lhs, ObjectId rhs)
        {
            return !(lhs == rhs);
        }

        /// <summary>
        /// Compares two ObjectIds.
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>True if the first ObjectId is greather than or equal to the second ObjectId.</returns>
        public static bool operator >=(ObjectId lhs, ObjectId rhs)
        {
            return lhs.CompareTo(rhs) >= 0;
        }

        /// <summary>
        /// Compares two ObjectIds.
        /// 比较两个ObjectId
        /// </summary>
        /// <param name="lhs">第一个ObjectId</param>
        /// <param name="rhs">第二个ObjectId</param>
        /// <returns>如果第一个Objectid大于第二个Objectid，则为true</returns>
        public static bool operator >(ObjectId lhs, ObjectId rhs)
        {
            return lhs.CompareTo(rhs) > 0;
        }
        #endregion

        #region 公开静态方法
        /// <summary>
        /// 生成具有唯一值的新ObjectId。
        /// </summary>
        /// <returns>An ObjectId.</returns>
        public static ObjectId GenerateNewId()
        {
            return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
        }

        /// <summary>
        /// 生成具有唯一值的新ObjectId（具有基于给定日期时间的时间戳组件）.
        /// </summary>
        /// <param name="timestamp">时间戳组件（表示为日期时间）。</param>
        /// <returns>An ObjectId.</returns>
        public static ObjectId GenerateNewId(DateTime timestamp)
        {
            return GenerateNewId(GetTimestampFromDateTime(timestamp));
        }

        /// <summary>
        /// 生成具有唯一值（具有给定时间戳）的新ObjectId。
        /// </summary>
        /// <param name="timestamp">时间戳组件。</param>
        /// <returns>An ObjectId.</returns>
        public static ObjectId GenerateNewId(int timestamp)
        {
            int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
            return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
        }

        /// <summary>
        /// 生成具有唯一值的新ObjectId字符串。
        /// </summary>
        /// <returns>新生成的ObjectId的字符串值。</returns>
        public static string GenerateNewStringId()
        {
            return GenerateNewId().ToString();
        }

        /// <summary>
        /// 将ObjectId的组件打包到字节数组中。
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <param name="machine">机器哈希</param>
        /// <param name="pid">PID.</param>
        /// <param name="increment">增量</param>
        /// <returns>字节数组</returns>
        public static byte[] Pack(int timestamp, int machine, short pid, int increment)
        {
            if ((machine & 0xff000000) != 0)
            {
                throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
            }
            if ((increment & 0xff000000) != 0)
            {
                throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
            }

            byte[] bytes = new byte[12];
            bytes[0] = (byte)(timestamp >> 24);
            bytes[1] = (byte)(timestamp >> 16);
            bytes[2] = (byte)(timestamp >> 8);
            bytes[3] = (byte)(timestamp);
            bytes[4] = (byte)(machine >> 16);
            bytes[5] = (byte)(machine >> 8);
            bytes[6] = (byte)(machine);
            bytes[7] = (byte)(pid >> 8);
            bytes[8] = (byte)(pid);
            bytes[9] = (byte)(increment >> 16);
            bytes[10] = (byte)(increment >> 8);
            bytes[11] = (byte)(increment);
            return bytes;
        }

        /// <summary>
        /// 分析字符串并创建新的ObjectId。
        /// </summary>
        /// <param name="s">字符串值</param>
        /// <returns>A ObjectId.</returns>
        public static ObjectId Parse(string s)
        {
            if (s == null)
            {
                throw new ArgumentNullException("s");
            }
            if (s.Length != 24)
            {
                throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters.");
            }
            return new ObjectId(ParseHexString(s));
        }
        /// <summary>
        /// Unpacks a byte array into the components of an ObjectId.
        /// 将字节数组解压到ObjectId的组件中。
        /// </summary>
        /// <param name="bytes">字节数组。</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="machine">机器哈希</param>
        /// <param name="pid">PID</param>
        /// <param name="increment">增量</param>
        public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            if (bytes.Length != 12)
            {
                throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
            }
            timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
            machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
            pid = (short)((bytes[7] << 8) + bytes[8]);
            increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
        }
        #endregion

        #region 私有静态方法
        /// <summary>
        ///获取当前进程id。此方法存在的原因是CAS如何在调用堆栈上操作，检查
        ///在执行方法之前获取权限。因此，如果我们内联这个调用，调用方法将不会执行
        ///在抛出一个异常之前，它要求try/catch处于一个更高的级别，而我们并不一定控制这个级别。
        /// </summary>
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int GetCurrentProcessId()
        {
            return Process.GetCurrentProcess().Id;
        }

        private static int GetMachineHash()
        {
            var hostName = Environment.MachineName; // use instead of Dns.HostName so it will work offline
            var md5 = MD5.Create();
            var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(hostName));
            return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash
        }

        private static int GetTimestampFromDateTime(DateTime timestamp)
        {
            return (int)Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds);
        }
        /// <summary>
        /// 将十六进制字符串解析为其等效的字节数组。
        /// </summary>
        /// <param name="s">要分析的十六进制字符串。</param>
        /// <returns>与十六进制字符串等效的字节。</returns>
        public static byte[] ParseHexString(string s)
        {
            if (s == null)
            {
                throw new ArgumentNullException("s");
            }

            if (s.Length % 2 == 1)
            {
                throw new Exception("The binary key cannot have an odd number of digits");
            }

            byte[] arr = new byte[s.Length >> 1];

            for (int i = 0; i < s.Length >> 1; ++i)
            {
                arr[i] = (byte)((GetHexVal(s[i << 1]) << 4) + (GetHexVal(s[(i << 1) + 1])));
            }

            return arr;
        }
        /// <summary>
        ///把字节数组转换成哈希字符串
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>哈希字符串</returns>
        public static string ToHexString(byte[] bytes)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            var result = new char[bytes.Length * 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                var val = _lookup32[bytes[i]];
                result[2 * i] = (char)val;
                result[2 * i + 1] = (char)(val >> 16);
            }
            return new string(result);
        }
        /// <summary>
        /// 将日期时间转换为自Unix纪元以来的毫秒数。
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <returns>自Unix时代以来的秒数。</returns>
        public static long ToMillisecondsSinceEpoch(DateTime dateTime)
        {
            var utcDateTime = ToUniversalTime(dateTime);
            return (utcDateTime - __unixEpoch).Ticks / 10000;
        }
        /// <summary>
        /// 将日期时间转换为UTC（对MinValue和MaxValue进行特殊处理）。
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <returns>UTC时间</returns>
        public static DateTime ToUniversalTime(DateTime dateTime)
        {
            if (dateTime == DateTime.MinValue)
            {
                return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
            }
            else if (dateTime == DateTime.MaxValue)
            {
                return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
            }
            else
            {
                return dateTime.ToUniversalTime();
            }
        }

        private static int GetHexVal(char hex)
        {
            int val = (int)hex;
            //For uppercase A-F letters:
            //return val - (val < 58 ? 48 : 55);
            //For lowercase a-f letters:
            //return val - (val < 58 ? 48 : 87);
            //Or the two combined, but a bit slower:
            return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
        }
        #endregion

        #region 公有方法
        /// <summary>
        /// 将此ObjectId与另一个ObjectId进行比较。
        /// </summary>
        /// <param name="other">另一个ObjectId.</param>
        /// <returns>32位有符号整数，指示此ObjectId是否小于、等于或大于另一个ObjectId。</returns>
        public int CompareTo(ObjectId other)
        {
            int r = _timestamp.CompareTo(other._timestamp);
            if (r != 0) { return r; }
            r = _machine.CompareTo(other._machine);
            if (r != 0) { return r; }
            r = _pid.CompareTo(other._pid);
            if (r != 0) { return r; }
            return _increment.CompareTo(other._increment);
        }

        /// <summary>
        /// 将此ObjectID与另一个ObjectID进行比较
        /// </summary>
        /// <param name="rhs">另一个ObjectID</param>
        /// <returns>如果两个objectid相等，则为True。</returns>
        public bool Equals(ObjectId rhs)
        {
            return
                _timestamp == rhs._timestamp &&
                _machine == rhs._machine &&
                _pid == rhs._pid &&
                _increment == rhs._increment;
        }
        /// <summary>
        /// 转换ObjectId成字节数组
        /// </summary>
        /// <returns>字节数组</returns>
        public byte[] ToByteArray()
        {
            return Pack(_timestamp, _machine, _pid, _increment);
        }
        #endregion

        #region 重写方法
        /// <summary>
        /// 将此ObjectID与另一个对象进行比较
        /// </summary>
        /// <param name="obj">另外一个对象</param>
        /// <returns>如果另一个对象是ObjectID且等于此对象，则为True。</returns>
        public override bool Equals(object obj)
        {
            if (obj is ObjectId)
            {
                return Equals((ObjectId)obj);
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 获取哈希代码。
        /// </summary>
        /// <returns>哈希代码</returns>
        public override int GetHashCode()
        {
            int hash = 17;
            hash = 37 * hash + _timestamp.GetHashCode();
            hash = 37 * hash + _machine.GetHashCode();
            hash = 37 * hash + _pid.GetHashCode();
            hash = 37 * hash + _increment.GetHashCode();
            return hash;
        }
        /// <summary>
        /// 返回值的字符串表示形式
        /// </summary>
        /// <returns>值的字符串表示形式。</returns>
        public override string ToString()
        {
            return ToHexString(ToByteArray());
        }
        #endregion

    }
}

